package com.chat.websocket;

import com.chat.websocket.domain.ChatInfoContainer;
import com.chat.websocket.http.HttpApi;
import com.google.gson.Gson;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MixedAttribute;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;
import io.netty.util.CharsetUtil;

import java.io.*;
import java.net.URLDecoder;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * @author 崔超
 * 2022/2/12
 */
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String wsUri;

    public HttpRequestHandler(String wsUri) {
        this.wsUri = wsUri;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        String uri = request.uri();
        uri = URLDecoder.decode(uri, Charset.defaultCharset());

        if (uri.startsWith("/static")) { //静态头像文件
            String resource = uri.substring(8);
            String fileUrl = ClassLoader.getSystemResource(resource).getPath();
            File file = new File(fileUrl);
            if (!file.exists() || !file.isFile()) {
                returnNotFound(ctx);
            } else {
                returnFile(ctx, request, file);
            }

        } else if (uri.startsWith("/upload")) { //用户上传文件
            String workPath = System.getProperty("user.home");
            String resource = uri.substring(7);
            File file = new File(workPath + resource);
            if (!file.exists() || !file.isFile()) {
                returnNotFound(ctx);
            } else {
                returnFile(ctx, request, file);
            }

        } else if (uri.startsWith(wsUri)) { //websocket连接
            String[] names = fetchName(uri);
            String roomName = names[0];
            String userName = names[1];

            ChatInfoContainer.enterChatRoom(ctx, roomName, userName);
            request.setUri(wsUri);
            ctx.fireChannelRead(request.retain());

        } else { //http请求
            String jsonResp = HttpApi.handleAPI(uri);
            if (jsonResp != null) {
                returnJson(ctx, jsonResp);
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    private void returnNotFound(ChannelHandlerContext ctx) {
        ByteBuf content = Unpooled.copiedBuffer("URL not found", CharsetUtil.UTF_8);
        HttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND, content);
        ChannelFuture future = ctx.writeAndFlush(response);
        future.addListener(ChannelFutureListener.CLOSE);
    }

    public void returnJson(ChannelHandlerContext ctx, String jsonStr) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK,
                Unpooled.copiedBuffer(jsonStr, CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
        response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());

        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "*");//允许headers自定义
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT,DELETE");
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    public void returnFile(ChannelHandlerContext ctx, FullHttpRequest request, File f) throws Exception {
        RandomAccessFile file = new RandomAccessFile(f, "r");
        io.netty.handler.codec.http.HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "image/*");
        boolean keepAlive = HttpUtil.isKeepAlive(request);
        if (keepAlive) {
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.write(response);

        if (ctx.pipeline().get(SslHandler.class) == null) {
            ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
        } else {
            ctx.write(new ChunkedNioFile(file.getChannel()));
        }

        ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!keepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private FileUploadSucInfo saveMultipartFile(FullHttpRequest request, String workPath) throws IOException {
        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request);
        FileUploadSucInfo fileInfo = new FileUploadSucInfo();
        while (decoder.hasNext()) {
            InterfaceHttpData data = decoder.next();
            if (data == null) {
                continue;
            }
            switch (data.getHttpDataType()) {
                case Attribute:
                    var attr = (MixedAttribute) data;
                    switch (attr.getName()) {
                        case "room":
                            fileInfo.roomName = attr.getValue();
                            break;
                        case "nickname":
                            fileInfo.nickName = attr.getValue();
                            break;
                    }
                    break;
                case FileUpload:
                    FileUpload fileUpload = (FileUpload) data;
                    String containDir = UUID.randomUUID().toString();
                    String dirPath = workPath + "/" + containDir;
                    String filename = fileUpload.getFilename();

                    File dir = new File(dirPath);
                    if (!dir.mkdir()) {
                        throw new IOException("创建文件夹失败");
                    }
                    File file = new File(dirPath + "/" + filename);
                    if (!file.exists() && !file.createNewFile()) {
                        throw new IOException("创建文件失败");
                    }
                    try (FileChannel inputChannel = new FileInputStream(fileUpload.getFile()).getChannel();
                         FileChannel outputChannel = new FileOutputStream(file).getChannel()) {
                        outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
                    }
                    fileInfo.path = "/" + containDir + "/" + filename;
                    fileInfo.fileName = filename;
                    break;
            }
            data.release();
        }
        return fileInfo;
    }

    private String[] fetchName(String uri) {
        String pattern = "/ws/(.*)/(.*)";
        Pattern r = Pattern.compile(pattern);
        Matcher m = r.matcher(uri);
        if (m.find()) {
            return new String[]{
                    m.group(1), m.group(2)};

        }
        throw new RuntimeException("url格式不对");
    }

    static class FileUploadSucInfo {
        String roomName;
        String fileName;
        String nickName;
        String path;
    }
}
